/* @(#)FseParagraph.java
 ** 
 ** Copyright (C) 2012 by Steven D. Stamps
 **
 **             Trademarks & Copyrights
 ** Flywheel Management Science(TM), Flywheel Management Model(TM),
 ** Flywheel Story Editor(TM) and FlywheelMS(TM) are exclusive trademarks
 ** of Steven D. Stamps and may only be used freely for the purpose of
 ** identifying the unforked version of this software.  Subsequent forks
 ** may not use these trademarks.  All other rights are reserved.
 **
 ** DecKanGL (Decorated Kanban Glyph Language) and TribKn (Tribal Knowledge)
 ** are also exclusive trademarks of Steven D. Stamps.  These may be used
 ** freely within the unforked FlywheelMS application and documentation.
 ** All other rights are reserved.
 **
 ** gConGUI (game Controller Graphical User Interface) is an exclusive
 ** trademark of Steven D. Stamps.  This trademark may be used freely
 ** within the unforked FlywheelMS application and documentation.
 ** All other rights are reserved.
 **
 ** Trademark information is available at
 ** <http://www.flywheelms.com/trademarks>
 **
 ** Flywheel Management Science(TM) is a copyrighted body of management
 ** metaphors, governance processes, and leadership techniques that is
 ** owned by Steven D. Stamps.  These copyrighted materials may be freely
 ** used, without alteration, by the community (users and developers)
 ** surrounding this GPL3-licensed software.  Additional copyright
 ** information is available at <http://www.flywheelms.org/copyrights>
 **
 **              GPL3 Software License
 ** This program is free software: you can use it, redistribute it and/or
 ** modify it under the terms of the GNU General Public License, version 3,
 ** as published by the Free Software Foundation. This program is distributed
 ** in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
 ** even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 ** PURPOSE.  See the GNU General Public License for more details. You should
 ** have received a copy of the GNU General Public License, in a file named
 ** COPYING, along with this program.  If you cannot find your copy, see
 ** <http://www.gnu.org/licenses/gpl-3.0.html>.
 */
package com.flywheelms.library.fse.views;

import java.util.Date;

import android.content.Context;
import android.text.ParcelableSpan;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.util.AttributeSet;
import android.widget.ImageView;
import android.widget.LinearLayout;

import com.flywheelms.library.fmm.enumerator.FmmLockStatus;
import com.flywheelms.library.fmm.node.NodeId;
import com.flywheelms.library.fmm.node.impl.nodefrag.NodeFragAuditBlock;
import com.flywheelms.library.fmm.node.interfaces.horizontal.FmmNodeAudit;
import com.flywheelms.library.fse.enumerator.FseContentModificationState;
import com.flywheelms.library.fse.enumerator.FseLockModificationState;
import com.flywheelms.library.fse.enumerator.FseParagraphContentType;
import com.flywheelms.library.fse.enumerator.FseParagraphNumberingStyle;
import com.flywheelms.library.fse.enumerator.FseParagraphStyle;
import com.flywheelms.library.fse.enumerator.FseStyleModificationState;
import com.flywheelms.library.fse.interfaces.FseParagraphContentView;
import com.flywheelms.library.fse.model.FseParagraph;
import com.flywheelms.library.fse.model.FseTemplateParagraph;
import com.flywheelms.library.fse.widget.FseParagraphSpinner;

public abstract class FseParagraphView extends LinearLayout
		implements FmmNodeAudit {

	protected FseParagraphEditor paragraphEditor;
	private FseParagraphSpinner spinner;
	protected FseParagraphContentView contentView;
	////////////
	FseParagraph baselineFseParagraph;
	////////////
	private String paragraphId;
	protected FseParagraphStyle style;
	private FseParagraphNumberingStyle numberingStyle;
	private ParcelableSpan[] additionalFormattingSpanArray;
	protected String initialText = "";
	private NodeFragAuditBlock auditBlock;
	private FseContentModificationState contentModificationState;
	private ImageView contentModificationStateImageView;
	private FseLockModificationState lockModificationState;
	private ImageView lockModificationStateImageView;
	private FseStyleModificationState styleModificationState;
	private ImageView styleModificationStateImageView;
	private boolean doNotChangeModificationState = true;

	public FseParagraphView(Context context, AttributeSet attrs) {
		super(context, attrs);
	}
	
	// used for instantiating from an existing FseDocument
	public void initialize(FseParagraphEditor aParagraphEditor, FseParagraph anFseParagraph) {
		initialize(
			aParagraphEditor,
			anFseParagraph.getParagraphId(),
			anFseParagraph.getStyle(),
			anFseParagraph.getNumberingStyle(),
			anFseParagraph.getTextContent(),
			anFseParagraph.getLockModificationState(),
			anFseParagraph.getContentModificationState(),
			anFseParagraph.getStyleModificationState(),
			anFseParagraph.getAuditBlock().getClone() );
		this.baselineFseParagraph = anFseParagraph;
	}

	// used to add a new paragraph to an existing document
	public void initialize(
			FseParagraphEditor aParagraphEditorView,
			FseParagraphStyle anFseParagraphStyle,
			FseParagraphNumberingStyle aNumberingStyle ) {
		String theParagraphId = FseParagraph.generateParagraphId();
		initialize(
			aParagraphEditorView, 
			theParagraphId,
			anFseParagraphStyle,
			aNumberingStyle,
			"",
			FseLockModificationState.UNCHANGED,
			FseContentModificationState.NEW,
			FseStyleModificationState.UNCHANGED,
			NodeFragAuditBlock.createNewAuditBlock(theParagraphId) );
	}

	// used for instantiating a new document from a template
	public void initialize(FseParagraphEditor aParagraphEditorView, FseTemplateParagraph anFseTemplateParagraph) {
		initialize(
			aParagraphEditorView,
			FseParagraph.generateParagraphId(),
			anFseTemplateParagraph.getParagraphStyle(),
			anFseTemplateParagraph.getParagraphStyle().getNumberingStyle(),
			anFseTemplateParagraph.getInitialText(),
			FseLockModificationState.UNCHANGED,
			FseContentModificationState.UNCHANGED,
			FseStyleModificationState.UNCHANGED,
			NodeFragAuditBlock.createNewAuditBlock(this.getDocumentView().getDocumentId()) );
	}

	// the "real" constructor
	public void initialize(
			FseParagraphEditor aParagraphEditorView,
			String aParagraphId,
			FseParagraphStyle anFseParagraphStyle,
			FseParagraphNumberingStyle aNumberingStyle,
			String anInitialTextString,
			FseLockModificationState aLockModificationState,
			FseContentModificationState aContentModificationState,
			FseStyleModificationState aStyleModificationState,
			NodeFragAuditBlock anAuditBlock ) {
		this.paragraphEditor = aParagraphEditorView;
		this.paragraphId = aParagraphId;
		this.style = anFseParagraphStyle;
		this.numberingStyle = aNumberingStyle;
		this.initialText = anInitialTextString;
		this.lockModificationStateImageView = (ImageView) getChildAt(0);
		this.contentModificationStateImageView = (ImageView) getChildAt(1);
		this.styleModificationStateImageView = (ImageView) getChildAt(2);
		setLockModificationState(aLockModificationState);
		setContentModificationState(aContentModificationState);
		setStyleModificationState(aStyleModificationState);
		this.auditBlock = anAuditBlock;
		this.spinner = (FseParagraphSpinner) getChildAt(3);
		this.spinner.initializeParagraphSpinner(this);
		initializeContentView();
	}

	abstract protected void initializeContentView();
	
	public String getParagraphId() {
		return this.paragraphId;
	}

	public FseParagraphContentTextView getContentTextView() {
		return isTextContent() ? (FseParagraphContentTextView) this.contentView : null;
	}
	
	public FseParagraphContentImageView getContentImageView() {
		return isImageContent() ? (FseParagraphContentImageView)this.contentView : null;
	}
	
	public FseParagraphContentDrawingPadView getContentDrawingView() {
		return isDrawingPadContent() ? (FseParagraphContentDrawingPadView)this.contentView : null;
	}

	public ParcelableSpan[] getStyleFormattingSpanArray() {
		return this.style.getFormattingArray();
	}

	public ParcelableSpan[] getAdditionalFormattingSpanArray() {
		return this.additionalFormattingSpanArray;
	}
	
	public void setAdditionalFormattingSpanArray(ParcelableSpan[] aParcelableSpanArray) {
		this.additionalFormattingSpanArray = aParcelableSpanArray;
	}

	public FseParagraphStyle getStyle() {
		return this.style;
	}
	
	public FseParagraphNumberingStyle getNetNumberingStyle() {
		if(this.numberingStyle.equals(FseParagraphNumberingStyle.DEFAULT)) {
			return this.style.getNumberingStyle();
		}
		return this.numberingStyle;
	}
	
	public FseParagraphNumberingStyle getRawNumberingStyle() {
		return this.numberingStyle;
	}

	public void setNumberingStyle(FseParagraphNumberingStyle numberingStyle) {
		this.numberingStyle = numberingStyle;
	}
	
	public boolean isNumbered() {
		if(this.numberingStyle.equals(FseParagraphNumberingStyle.DEFAULT)) {
			return ! this.style.getNumberingStyle().equals(FseParagraphNumberingStyle.NONE);
		}
		return ! this.numberingStyle.equals(FseParagraphNumberingStyle.NONE);
	}

	public void changeStyle(FseParagraphStyle aParagraphStyle) {
		if(aParagraphStyle == this.style) {
			return;
		}
		this.style = aParagraphStyle;
		this.getSpinner().setStyle(this.style);
		if(aParagraphStyle.getContentType() == FseParagraphContentType.TEXT) {
			String theParagraphContentString = getContentTextView().getText().toString();
			Spannable theSpannable = this.style.getSpannable(theParagraphContentString);
			getContentTextView().setText(theSpannable);
		}
		// needs to be updated to maintain additionalFormattingSpanArray
		this.contentView.setPadding(aParagraphStyle.getLeftMargin(), aParagraphStyle.getTopMargin(), 0, 0);
		updateStyleModificationState();
	}

	public FseParagraphNumberingStyle getNumberingStyle() {
		return this.numberingStyle;
	}

	public String getInitialText() {
		return this.initialText;
	}

	@Override
	public String toString() {
		return this.style.getName() + " : "
				+ this.contentView.toString();
	}

	public FseParagraphContentView getContentView() {
		return this.contentView;
	}
	
	public FseParagraphSpinner getSpinner() {
		return this.spinner;
	}

	public CharSequence cutTextToEnd() {
		int theParagraphTextLength = this.getContentTextView().length();
		if(theParagraphTextLength == 0) {
			return "";
		}
		int theCursorPosition = this.getContentTextView().getSelectionStart();
		if(theParagraphTextLength == theCursorPosition) {
			return "";
		}
		CharSequence theRemainingText =
				this.getContentTextView().getEditableText().subSequence(0, theCursorPosition);
		CharSequence theCutText =
				this.getContentTextView().getEditableText().subSequence(theCursorPosition, theParagraphTextLength);
		this.getContentTextView().setText(theRemainingText);
		SpannableStringBuilder theSpannableStringBuilder = new SpannableStringBuilder(theCutText);
		theSpannableStringBuilder.clearSpans();
		return theSpannableStringBuilder;
	}

	public boolean isTextContent() {
		return this.style.getContentViewClass() == FseParagraphContentTextView.class ? true : false;
	}

	public boolean isImageContent() {
		return this.style.getContentViewClass() == FseParagraphContentImageView.class ? true : false;
	}

	public boolean isDrawingPadContent() {
		return this.style.getContentViewClass() == FseParagraphContentDrawingPadView.class ? true : false;
	}

	public FseParagraphEditor getParagraphEditorView() {
		return this.paragraphEditor;
	}

	public boolean isDescendentStyle(FseParagraphView aParagraphView) {
		return this.style.isDescendentStyle(aParagraphView.getStyle());
	}

	public boolean isChildStyle(FseParagraphView aParagraphView) {
		return this.style.isChildStyle(aParagraphView.getStyle());
	}

	public boolean isParentStyle(FseParagraphView aParagraphView) {
		return this.style.isParentStyle(aParagraphView.getStyle());
	}

	public boolean isPeerStyle(FseParagraphView aParagraphView) {
		return this.style.isPeerStyle(aParagraphView.getStyle());
	}

	public void styleIndent() {
		changeStyle(this.style.getIndentStyle());
	}

	public void styleOutdent() {
		changeStyle(this.style.getOutdentStyle());
	}

	public void stylePromote() {
		changeStyle(this.style.getPromotionStyle());
	}

	public void styleDemote() {
		changeStyle(this.style.getDemotionStyle());
	}

	public boolean isFirst() {
		return this == getParagraphEditorView().getParagraphViewList().getFirst();
	}

	public boolean isLast() {
		return this == getParagraphEditorView().getParagraphViewList().getLast();
	}

	public FseDocumentView getDocumentView() {
		return this.paragraphEditor.getDocumentView();
	}
	
	public boolean hasEditorFocus() {
		return this.paragraphEditor.getParagraphViewFocus() == this;
	}
	
	public boolean hasSpinnerFocus() {
		return this.paragraphEditor.getParagraphViewSelectionList().contains(this);
	}
	
	////  START Audit Block  Wrapper ////
	
	@Override
	public NodeFragAuditBlock getAuditBlock() {
		return this.auditBlock;
	}

	@Override
	public void setAuditBlock(NodeFragAuditBlock anAuditBlock) {
		this.auditBlock = anAuditBlock;
	}
	
	//  Created  //
	
	@Override
	public NodeId getCreatedBy() {
		return this.auditBlock.getCreatedBy();
	}
	
	@Override
	public String getCreatedByNodeIdString() {
		return this.auditBlock.getCreatedBy().getNodeIdString();
	}

	@Override
	public void setCreatedBy(NodeId aNodeId) {
		this.auditBlock.setCreatedBy(aNodeId);
	}
	
	@Override
	public void setCreatedBy(String aNodeIdString) {
		this.auditBlock.setCreatedBy(aNodeIdString);
	}

	@Override
	public Date getCreatedTimestamp() {
		return this.auditBlock.getCreatedTimestamp();
	}
	
	@Override
	public void setCreatedTimestamp(Date aCreatedTimestamp) {
		this.auditBlock.setCreatedTimestamp(aCreatedTimestamp);
	}
	
	//  Updated  //

	@Override
	public NodeId getLastUpdatedBy() {
		return this.auditBlock.getLastUpdatedBy();
	}

	@Override
	public String getLastUpdatedByNodeIdString() {
		return this.auditBlock.getLastUpdatedBy().getNodeIdString();
	}

	@Override
	public void setLastUpdatedBy(NodeId aNodeId) {
		this.auditBlock.setLastUpdatedBy(aNodeId);
	}

	@Override
	public void setLastUpdatedBy(String aNodeIdString) {
		this.auditBlock.setLastUpdatedBy(aNodeIdString);
	}

	@Override
	public Date getLastUpdatedTimestamp() {
		return this.auditBlock.getLastUpdatedTimestamp();
	}

	@Override
	public void setLastUpdatedTimestamp(Date aLastUpdate) {
		this.auditBlock.setLastUpdatedTimestamp(aLastUpdate);
	}
	
	//  Locked  //

	@Override
	public NodeId getLockedBy() {
		return this.auditBlock.getLockedBy();
	}

	@Override
	public String getLockedByNodeIdString() {
		return this.auditBlock.getLockedBy().getNodeIdString();
	}

	@Override
	public void setLockedBy(NodeId aNodeId) {
		this.auditBlock.setLockedBy(aNodeId);
	}
	
	@Override
	public void setLockedBy(String aNodeIdString) {
		this.auditBlock.setLockedBy(aNodeIdString);
	}

	@Override
	public Date getLockedTimestamp() {
		return this.auditBlock.getLockedTimestamp();
	}
	
	@Override
	public void setLockedTimestamp(Date aLockedTimestamp) {
		this.auditBlock.setLockedTimestamp(aLockedTimestamp);
	}

	@Override
	public void lock() {
		this.auditBlock.lock();
		updateLockModificationState();
	}

	@Override
	public void unlock() {
		this.auditBlock.unlock();
		updateLockModificationState();
	}
	
	@Override
	public void setLocked(boolean isLocked) {
		if(this.auditBlock.isLocked() == isLocked) {
			return;
		}
		if(isLocked) {
			this.auditBlock.lock();
		} else {
			this.auditBlock.unlock();
		}
		updateLockModificationState();
	}

	@Override
	public boolean isLocked() {
		return this.auditBlock.isLocked();
	}
	
	@Override
	public FmmLockStatus getLockStatus() {
		return getAuditBlock().getLockStatus();
	}
	
	////  END Audit Block Wrapper ////
	
	public void enableModificationStateUpdates() {
		this.doNotChangeModificationState = false;
	}

	public boolean stillBuildingDocument() {
		return this.doNotChangeModificationState;
	}
	
	////////////////////////////////

	public boolean isContentModified() {
		boolean isModified = false;
		switch (this.style.getContentType()) {
		case TEXT:
			isModified = isTextContentModified();
			break;
		case IMAGE:
			break;
		case DRAWING:
			break;
		default:
			// TODO - throw a nasty exception about bad programmers!  ;-)
		}
		// TODO Auto-generated method stub
		return isModified;
	}

	public boolean isTextContentModified() {
		return (isTextContent() ? ! this.initialText.equals(getContentTextView().getText().toString()) : false);
	}
	
	public boolean isNew() {
		return getContentModificationState() == FseContentModificationState.NEW;
	}

	public void updateContentModificationState() {
		if(this.doNotChangeModificationState) {
			return;
		}
		boolean theTextIsModified = isTextContentModified();
		switch(this.contentModificationState) {
		case NEW:
			return;
		case MODIFIED:
			if(! theTextIsModified) {
				setContentModificationState(FseContentModificationState.UNCHANGED);
			}
			return;
		case UNCHANGED:
			if(theTextIsModified) {
				setContentModificationState(FseContentModificationState.MODIFIED);
			}
			return;
		default:
			// TODO - throw a nasty exception about bad programmers!  ;-)
		}
	}

	public FseContentModificationState getContentModificationState() {
		return this.contentModificationState;
	}

	public void setContentModificationState(FseContentModificationState aModificationState) {
		if(this.contentModificationState == aModificationState) {
			return;
		}
		this.contentModificationState = aModificationState;
		this.contentModificationStateImageView.setImageResource(aModificationState.getDrawableResourceId());
		this.paragraphEditor.updateContentModificationStateOnInput(aModificationState);
	}

	public void resetContentModificationState() {
		if(this.contentModificationState == FseContentModificationState.UNCHANGED) {
			return;
		}
		setContentModificationState(FseContentModificationState.UNCHANGED);
	}
	
	////

	public boolean isLockModified() {
		boolean isModified;
		if(this.baselineFseParagraph == null) {
			isModified = this.auditBlock.isLocked();
		} else {
			isModified = this.auditBlock.isLocked() != this.baselineFseParagraph.getAuditBlock().isLocked();
		}
		return isModified;
	}

	public void updateLockModificationState() {
		if(this.doNotChangeModificationState) {
			return;
		}
		boolean theLockIsModified = isLockModified();
		switch(this.lockModificationState) {
		case MODIFIED:
			if(! theLockIsModified) {
				setLockModificationState(FseLockModificationState.UNCHANGED);
			}
			return;
		case UNCHANGED:
			if(theLockIsModified) {
				setLockModificationState(FseLockModificationState.MODIFIED);
			}
			return;
		default:
			// TODO - throw a nasty exception about bad programmers!  ;-)
		}
	}

	public FseLockModificationState getLockModificationState() {
		return this.lockModificationState;
	}

	public void setLockModificationState(FseLockModificationState aModificationState) {
		if(this.lockModificationState == aModificationState) {
			return;
		}
		this.lockModificationState = aModificationState;
		this.lockModificationStateImageView.setBackgroundColor(getResources().getColor(aModificationState.getColorResourceId()));
		this.paragraphEditor.updateLockModificationState(aModificationState);
	}

	public void resetLockModificationState() {
		if(this.lockModificationState == FseLockModificationState.UNCHANGED) {
			return;
		}
		setLockModificationState(FseLockModificationState.UNCHANGED);
	}
	
	////

	public boolean isStyleModified() {
		boolean isModified;
		if(this.baselineFseParagraph == null) {
			isModified = false;
		} else {
			isModified = this.style != this.baselineFseParagraph.getStyle();
		}
		return isModified;
	}
	
	public void updateStyleModificationState() {
		if(this.doNotChangeModificationState) {
			return;
		}
		boolean theStyleIsModified = isStyleModified();
		switch(this.styleModificationState) {
		case MODIFIED:
			if(! theStyleIsModified) {
				setStyleModificationState(FseStyleModificationState.UNCHANGED);
			}
			return;
		case UNCHANGED:
			if(theStyleIsModified) {
				setStyleModificationState(FseStyleModificationState.MODIFIED);
			}
			return;
		default:
			// TODO - throw a nasty exception about bad programmers!  ;-)
		}
	}
	
	public FseStyleModificationState getStyleModificationState() {
		return this.styleModificationState;
	}

	public void setStyleModificationState(FseStyleModificationState aModificationState) {
		if(this.styleModificationState == aModificationState) {
			return;
		}
		this.styleModificationState = aModificationState;
		this.styleModificationStateImageView.setBackgroundColor(getResources().getColor(aModificationState.getColorResourceId()));
		this.paragraphEditor.updateStyleModificationState(aModificationState);
	}

	public void resetStyleModificationState() {
		if(this.styleModificationState == FseStyleModificationState.UNCHANGED) {
			return;
		}
		setStyleModificationState(FseStyleModificationState.UNCHANGED);
	}
	
	////
	
	public void resetModificationState() {
		resetContentModificationState();
		resetLockModificationState();
		resetStyleModificationState();
	}

	public void setAsNewBaseline() {
		this.baselineFseParagraph = generateFseParagraph();
		resetModificationState();
	}
	
	public FseParagraph generateFseParagraph() {
		FseParagraph theParagraph = new FseParagraph(getParagraphId(), getStyle(), getNetNumberingStyle(), getContentTextView().getText().toString());
		theParagraph.setLockModificationState(getLockModificationState());
		theParagraph.setContentModificationState(getContentModificationState());
		theParagraph.setStyleModificationState(getStyleModificationState());
		theParagraph.setCreatedBy(getCreatedBy());
		theParagraph.setCreatedTimestamp(getCreatedTimestamp());
		theParagraph.setLastUpdatedTimestamp(getLastUpdatedTimestamp());
		theParagraph.setLastUpdatedBy(getLastUpdatedBy());
		theParagraph.setLockedTimestamp(getLockedTimestamp());
		theParagraph.setLockedBy(getLockedBy());
		theParagraph.setLocked(isLocked());
		return theParagraph;
	}

}
